//#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#include "lib.h"
#include "stream.h"
#include "rresource.h"
#include "rentry.h"
#include "filesystem.h"
using namespace Library;

#include "zlib.h"


/**
 * Creates an empty, non-persistent resource entry useful for loading
 * from disk a resource header via load() and its resource data loadData().
 */
ResourceEntry::ResourceEntry()
	: m_name()
{
	m_size				= 0;
	m_data				= 0;
	m_usecount			= 0;
	m_userdata			= 0;
	m_timestamp			= 0;

	m_isizecompressed	= 0;
	m_istream			= 0;
	m_ioffset			= 0;
}

/**
 * Cleans and deallocates itself, but not the user data.  It is the
 * responsibility of the application which populates user data to
 * delete/deallocate/unassign user data as the use count drops to zero (0).
 */
ResourceEntry::~ResourceEntry()
{
	if (m_data)
		Resource::deallocate(m_data);
}

/**
 * @return		A pointer to the resource data on success, 0 on failure.
 *
 * Retrieves a pointer to the resource data and increments the use count.  If
 * the resource is not currently in memory, the resource will be loaded from
 * the stream specified during ResourceEntry::load().
 */
void *
ResourceEntry::acquire()
{
	if (!m_data)
		loadData();

	if (m_data)
	{
		m_usecount++;
		return m_data;
	}

	return 0;
}

/**
 * @return		@b true on success, @b false on failure.
 *
 * Decrements the use count.  If the use count falls to zero (0), the resource
 * data is freed from memory.
 */
bool8
ResourceEntry::release()
{
	if (m_usecount)
	{
		if (--m_usecount == 0)
		{
			Resource::deallocate(m_data);
			m_data = 0;
		}

		return true;
	}

	return false;
}

#if 0

#include <stdio.h>
#include <time.h>

void
ResourceEntry::printHeader()
{
	wprintf(TEXT("\nDate Modified    Compressed?    Size  Size on Disk  Offset       Name\n"));
	wprintf(TEXT("-------------------------------------------------------------------------------\n"));
}

void
ResourceEntry::printEntry()
{
	String timestamp;
	timestamp = asctime(localtime((time_t*)&m_timestamp));
	timestamp.replace("\r", "");
	timestamp.replace("\n", "");

	wprintf(TEXT("%s   %d %8ld   %8ld  %8ld  %s\n"),
		(const uint16*)timestamp,
		(m_isizecompressed != 0),
		m_size,
		m_isizecompressed,
		m_ioffset,
		(const uint16*)m_name);
}

#endif

/**
 * @param is	Pointer to a stream from which the resource header should be
 *				loaded.
 *
 * @return		@b true on success, @b false on failure.
 *
 * Loads the resource header from stream @e os starting at the current stream's
 * file offset.  The stream's file offset is advanced by the number of bytes
 * read from the stream; equal to the size of the resource header upon success
 * and something less than the size of the resource header, maybe even 0, upon
 * failure.  This means it is the calling class' responsibility to save and
 * restore the stream's offset in the event of failure.
 */
bool8
ResourceEntry::load(pStream is, uint8 format)
{
	switch (format)
	{
		// ResourceEntry::DiskImage_versionD
		case RE_DISKIMAGEFORMAT_VERSION_D:
		{
			DiskImage_versionD	di;
			char * 				name;

			if (is->read(&di, sizeof di) != sizeof di)
				return false;

			m_ioffset			= di.m_offset;
			m_size				= di.m_size;
			m_isizecompressed	= di.m_sizecompressed;
			m_timestamp			= di.m_timestamp;

			// In an attempt minimize the chances of a stack overflow, due to
			// a corrupt resource file, we artifically restrict the resource
			// name length.
			if (di.m_namelength >= FileSystem::maxPath())
				return false;

			// If _alloca fails it will generate a structured stack overflow
			// exception, so the 'if' statement below is worthless ... but it
			// makes me feel better.  -kral  07.12.2004
			name = (char *)_alloca(di.m_namelength + 1);
			if (!name)
				return false;

			if (is->read(name, di.m_namelength) != (int32)di.m_namelength)
				return false;

			name[di.m_namelength] = 0;
			m_name = name;

			break;
		}

		// ResourceEntry::DiskImage_versionC
		case RE_DISKIMAGEFORMAT_VERSION_C:
		{
			DiskImage_versionC	di;

			if (is->read(&di, sizeof di) != sizeof di)
				return false;

			m_name				= di.m_name;
			m_ioffset			= di.m_offset;
			m_size				= di.m_size;
			m_isizecompressed	= di.m_sizecompressed;
			m_timestamp			= 0;

			break;
		}

		// ResourceEntry::DiskImage_versionA
		case RE_DISKIMAGEFORMAT_VERSION_A:
		{
			DiskImage_versionA	di;

			if (is->read(&di, sizeof di) != sizeof di)
				return false;

			m_name				= di.m_name;
			m_ioffset			= di.m_offset;
			m_size				= di.m_size;
			m_isizecompressed	= 0;
			m_timestamp			= 0;

			break;
		}

		// Unknown disk image format.
		default:
		{
			return false;
			break;
		}
	}

	m_data		= 0;
	m_usecount	= 0;
	m_userdata	= 0;
	m_istream	= is;

	return true;
}

/**
 * @param os	Pointer to a stream from which the resource data should be
 *				loaded.
 * @return		@b true on success, @b false on failure.
 *
 * Loads the resource data from stream @e os starting at the current stream's
 * file offset.  The stream's file offset is advanced by the number of bytes
 * read in from the stream; equal to the size of the resource data upon success
 * and something less than the size of the resource data, maybe even 0, upon
 * failure.  This means it is the calling class' responsibility to save and
 * restore the stream's offset in the event of failure.
 *
 * <b>Developer Notes:</b>
 *
 * Data space is allocated if needed by calling Resource::allocate(), otherwise
 * any previously allocated data space will be used (NOTE: data size is fixed
 * so buffer overrun shouldn't take place).  Should failure occur, the data
 * space will be deallocated, even if it was previously allocated, and set to
 * 0.  This is by design.
 */
bool8
ResourceEntry::loadData()
{
	uint8 * data;
	uint32	size;

	if (!m_istream || !m_ioffset)
		return false;

	if (m_data == NULL  &&  (m_data = Resource::allocate(this, m_size)) == NULL)
		return false;

	if (m_isizecompressed == 0)
	{
		if (m_istream->seek(m_ioffset, Stream::beginning) != (int32)m_ioffset ||
			m_istream->read(m_data, m_size) != (int32)m_size)
		{
			Resource::deallocate(m_data);
			m_data = 0;
		}
	}
	else
	{
		size = m_size;
		data = (uint8 *)malloc(m_isizecompressed);
		if (data)
		{
			if (m_istream->seek(m_ioffset, Stream::beginning) != (int32)m_ioffset ||
				m_istream->read(data, m_isizecompressed) != (int32)m_isizecompressed ||
				uncompress((uint8 *)m_data, &size, data, m_isizecompressed) != Z_OK ||
				m_size != size)
			{
				Resource::deallocate(m_data);
				m_data = 0;
			}

			free(data);
			data = 0;
		}
		else
		{
			Resource::deallocate(m_data);
			m_data = 0;
		}
	}

	return (bool8)(m_data != 0);
}


//----------------------------------------------------------------------------
// Private
//

/**
 * @fn ResourceEntry::ResourceEntry(ResourceEntry & src)
 * @param src	Reference to resource entry which should be copied.
 *
 * This method is reserved and unimplemented.  It has been defined for
 * Orthodox Canonical Form (OCF) compliance to prevent shallow object copies
 * from inadvertent use.
 */

/**
 * @fn ResourceEntry & ResourceEntry::operator=(ResourceEntry & src)
 * @param src	Reference to resource entry which should be copied.
 *
 * This method is reserved and unimplemented.  It has been defined for
 * Orthodox Canonical Form (OCF) compliance to prevent shallow object copies
 * from inadvertent use.
 */

//
// vim: nowrap
//
